package com.stars.easyms.feign.response;

import com.alibaba.fastjson.JSON;
import com.stars.easyms.base.constant.HttpHeaderConstants;
import com.stars.easyms.base.http.EasyMsResponseEntity;
import com.stars.easyms.base.util.DateTimeUtil;
import com.stars.easyms.base.util.EasyMsParameterizedType;
import com.stars.easyms.base.util.EasyMsUtil;
import com.stars.easyms.feign.exception.BusinessFeignException;
import feign.Request;
import feign.Response;
import feign.codec.Decoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.support.ResponseEntityDecoder;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 为feign增加response解析
 *
 * @author guoguifang
 * @date 2020-08-17 12:34
 * @since 1.6.1
 */
@Slf4j
public class EasyMsFeignDecoder extends ResponseEntityDecoder {

    private static final Map<Type, Boolean> EASY_MS_RESPONSE_ENTITY_TYPE_CACHE = new ConcurrentHashMap<>(32);

    private Decoder easyMsDecoder;

    public EasyMsFeignDecoder(Decoder easyMaDecoder, Decoder decoder) {
        super(decoder);
        this.easyMsDecoder = easyMaDecoder;
    }

    @Override
    public Object decode(final Response response, Type type) throws IOException {

        // 获取响应头信息
        Request request = response.request();
        Map<String, Collection<String>> requestHeaders = request.headers();
        Map<String, Collection<String>> responseHeaders = response.headers();
        String requestId = withUnknown(getValueFromHeader(requestHeaders, HttpHeaderConstants.HEADER_KEY_REQUEST_ID));
        String requestSys = withUnknown(getValueFromHeader(requestHeaders, HttpHeaderConstants.HEADER_KEY_REQUEST_SYS));
        String responseSys = getValueFromHeader(responseHeaders, requestHeaders, HttpHeaderConstants.HEADER_KEY_RESPONSE_SYS);
        String requestPath = getValueFromHeader(requestHeaders, HttpHeaderConstants.HEADER_KEY_REQUEST_PATH);
        if (requestPath == null) {
            requestPath = response.request().url();
        }

        // 判断是否是easy-ms的响应体，如果不是easy-ms的响应体则直接使用流的方式读取，如果解析错误则使用父类的解析
        boolean isEasyMsResponse = getValueFromHeader(responseHeaders, HttpHeaderConstants.HEADER_KEY_EASY_MS) != null;
        if (!isEasyMsResponse) {
            return decodeNonEasyMsResponse(response, type, requestPath, requestSys, responseSys, requestId);
        }

        // 把response解析成EasyMsResponseEntity类型
        Object object;
        try {
            if (isEasyMsResponseEntityType(type)) {
                object = easyMsDecoder.decode(response, type);
            } else {
                EasyMsParameterizedType parameterizedType = new EasyMsParameterizedType(new Type[]{type}, null, EasyMsResponseEntity.class);
                object = easyMsDecoder.decode(response, parameterizedType);
            }
        } catch (Exception e) {

            // 如果解析EasyMsResponseEntityType类型失败则使用非easy-ms模式解析
            return decodeNonEasyMsResponse(response, type, requestPath, requestSys, responseSys, requestId);
        }

        // 记录请求日志
        log.info("[调用服务-响应]-[请求地址:{}]-[请求系统:{}]-[服务系统:{}]-[请求ID:{}]-[响应时间:{}]-[接收响应时间:{}]-响应数据:{}",
                requestPath, requestSys, responseSys, requestId,
                withUnknown(getValueFromHeader(responseHeaders, HttpHeaderConstants.HEADER_KEY_RESPONSE_TIME)),
                DateTimeUtil.now(), object instanceof EasyMsResponseEntity ? object : JSON.toJSONString(object));

        // 如果返回的Type不是EasyMsResponseEntity类型的，则判断是否成功，如果返回的信息不是正确信息，则抛出异常
        if (!isEasyMsResponseEntityType(type) && object instanceof EasyMsResponseEntity) {
            EasyMsResponseEntity<?> responseEntity = (EasyMsResponseEntity) object;
            if (!responseEntity.isSuccess()) {
                throw new BusinessFeignException(response.status(), responseEntity.getErrorDesc(), response.request(),
                        responseEntity.getRetCode(), responseEntity.getRetMsg(), responseEntity.getErrorDesc());
            }
            if (responseEntity.getBody() != null) {
                return responseEntity.getBody();
            }
            return type == Boolean.class || type == boolean.class ? Boolean.TRUE : null;
        }
        return object;
    }

    private Object decodeNonEasyMsResponse(final Response response, Type type, String requestPath, String requestSys,
                                           String responseSys, String requestId) throws IOException {
        try (InputStream inStream = response.body().asInputStream()) {
            // 从响应流中获取响应数据
            StringBuilder sb = new StringBuilder();
            byte[] buf = new byte[1024];
            for (int n; (n = inStream.read(buf)) != -1; ) {
                sb.append(new String(buf, 0, n, StandardCharsets.UTF_8));
            }
            String result = sb.toString();

            // 记录响应日志信息
            log.info("[调用服务-响应]-[请求地址:{}]-[请求系统:{}]-[服务系统:{}]-[请求ID:{}]-[接收响应时间:{}]-响应数据:{}",
                    requestPath, requestSys, responseSys, requestId, DateTimeUtil.now(), result);

            // 如果返回类型为String则直接返回，如果不是String则使用fastJson转换成对应类型的对象返回
            if (type == String.class) {
                return result;
            }
            return JSON.parseObject(result, type);
        } catch (Exception e) {

            // 记录响应日志信息
            log.info("[调用服务-响应]-[请求地址:{}]-[请求系统:{}]-[服务系统:{}]-[请求ID:{}]-[接收响应时间:{}]",
                    requestPath, requestSys, responseSys, requestId, DateTimeUtil.now());

            // 如果使用流式解析异常则使用父类的解析器解析
            return super.decode(response, type);
        }
    }

    /**
     * 判断是否是EasyMsResponseEntity类型，EasyMsResponseEntity结构的也算EasyMsResponseEntity类型，为了兼容非easy-ms的调用方
     */
    private boolean isEasyMsResponseEntityType(Type type) {
        return EASY_MS_RESPONSE_ENTITY_TYPE_CACHE.computeIfAbsent(type, EasyMsUtil::isEasyMsResponseEntityType);
    }

    private String getValueFromHeader(Map<String, Collection<String>> responseHeaders,
                                      Map<String, Collection<String>> requestHeaders, String key) {
        String value = getValueFromHeader(responseHeaders, key);
        if (value == null) {
            value = getValueFromHeader(requestHeaders, key);
        }
        return withUnknown(value);
    }

    private String getValueFromHeader(Map<String, Collection<String>> headers, String key) {
        Collection<String> collection = headers.get(key);
        if (collection != null && !collection.isEmpty()) {
            return collection.toArray(new String[0])[0];
        }
        return null;
    }

    private String withUnknown(String str) {
        return str == null ? "unknown" : str;
    }

}
